# -*- mode: perl -*-
# ============================================================================

package Net::SNMP;

# $Id: SNMP.pm,v 3.0.1.1 2000-01-01 17:59:46 dtown Exp $
# $Source: /home/dtown/Projects/Net-SNMP/lib/Net/SNMP.pm,v $

# The module Net::SNMP implements an object oriented interface to the Simple
# Network Management Protocol.  Perl applications can use the module to 
# retrieve or update information on a remote host using the SNMP protocol.
# Both SNMPv1 and SNMPv2c (Community-Based SNMPv2) are supported by the 
# module.  The Net::SNMP module assumes that the user has a basic 
# understanding of the Simple Network Management Protocol and related 
# network management concepts.

# Copyright (c) 1998-2000 David M. Town <dtown@fore.com>.
# All rights reserved.

# This program is free software; you may redistribute it and/or modify it
# under the same terms as Perl itself.

# ============================================================================

## Global variables 

use vars qw(
   @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @ASN1 @ASN1_PDU @GENERICTRAP @SNMP 
   $DEBUG $FSM $REQUEST_ID $SOCKET $VERSION  
);

## Version of Net::SNMP module

$Net::SNMP::VERSION = 3.01;

use strict;

## Required version of Perl

require 5.003;

## Handle exporting of symbols

use Exporter();

@ISA = qw(Exporter);

@ASN1 = qw(
   INTEGER INTEGER32 OCTET_STRING NULL OBJECT_IDENTIFIER IPADDRESS  
   COUNTER COUNTER32 GAUGE GAUGE32 UNSIGNED32 TIMETICKS OPAQUE 
   COUNTER64 NOSUCHOBJECT NOSUCHINSTANCE ENDOFMIBVIEW 
);

@ASN1_PDU = qw(
   GET_REQUEST GET_NEXT_REQUEST GET_RESPONSE SET_REQUEST TRAP
   GET_BULK_REQUEST INFORM_REQUEST SNMPV2_TRAP
);

@GENERICTRAP = qw(
   COLD_START WARM_START LINK_DOWN LINK_UP AUTHENTICATION_FAILURE 
   EGP_NEIGHBOR_LOSS ENTERPRISE_SPECIFIC 
);

@SNMP = qw(
   SNMP_VERSION_1 SNMP_VERSION_2C SNMP_PORT SNMP_TRAP_PORT snmp_debug 
   oid_context_match oid_lex_sort ticks_to_time
);

@EXPORT    = (@ASN1, 'snmp_event_loop');
@EXPORT_OK = (@ASN1_PDU, @GENERICTRAP, @SNMP, 'SEQUENCE', 'snmp_one_event');

%EXPORT_TAGS = (
   asn1        => [@ASN1, @ASN1_PDU, 'SEQUENCE'],
   generictrap => [@GENERICTRAP], 
   snmp        => [@SNMP, 'snmp_event_loop'],
   ALL         => [@EXPORT, @EXPORT_OK]
);

## Import socket() defines and structure manipulators

use Socket qw(PF_INET SOCK_DGRAM inet_aton inet_ntoa sockaddr_in);

sub IPPROTO_UDP()         {   17 }

## ASN.1 Basic Encoding Rules type definitions

sub INTEGER()             { 0x02 }  # INTEGER      
sub INTEGER32()           { 0x02 }  # Integer32           - SNMPv2c
sub OCTET_STRING()        { 0x04 }  # OCTET STRING
sub NULL()                { 0x05 }  # NULL       
sub OBJECT_IDENTIFIER()   { 0x06 }  # OBJECT IDENTIFIER  
sub SEQUENCE()            { 0x30 }  # SEQUENCE       

sub IPADDRESS()           { 0x40 }  # IpAddress     
sub COUNTER()             { 0x41 }  # Counter  
sub COUNTER32()           { 0x41 }  # Counter32           - SNMPv2c 
sub GAUGE()               { 0x42 }  # Gauge     
sub GAUGE32()             { 0x42 }  # Gauge32             - SNMPv2c
sub UNSIGNED32()          { 0x42 }  # Unsigned32          - SNMPv2c 
sub TIMETICKS()           { 0x43 }  # TimeTicks  
sub OPAQUE()              { 0x44 }  # Opaque   
sub COUNTER64()           { 0x46 }  # Counter64           - SNMPv2c 

sub NOSUCHOBJECT()        { 0x80 }  # noSuchObject        - SNMPv2c 
sub NOSUCHINSTANCE()      { 0x81 }  # noSuchInstance      - SNMPv2c 
sub ENDOFMIBVIEW()        { 0x82 }  # endOfMibView        - SNMPv2c 
 
sub GET_REQUEST()         { 0xa0 }  # GetRequest-PDU    
sub GET_NEXT_REQUEST()    { 0xa1 }  # GetNextRequest-PDU 
sub GET_RESPONSE()        { 0xa2 }  # GetResponse-PDU
sub SET_REQUEST()         { 0xa3 }  # SetRequest-PDU
sub TRAP()                { 0xa4 }  # Trap-PDU
sub GET_BULK_REQUEST()    { 0xa5 }  # GetBulkRequest-PDU  - SNMPv2c 
sub INFORM_REQUEST()      { 0xa6 }  # InformRequest-PDU   - SNMPv2c 
sub SNMPV2_TRAP()         { 0xa7 }  # SNMPv2-Trap-PDU     - SNMPv2c 

## SNMP generic definitions 

sub SNMP_VERSION_1()      { 0x00 }  # RFC 1157 SNMP
sub SNMP_VERSION_2C()     { 0x01 }  # RFCs 1901, 1905, and 1906 SNMPv2c
sub SNMP_PORT()           {  161 }  # RFC 1157 standard UDP port for PDUs
sub SNMP_TRAP_PORT()      {  162 }  # RFC 1157 standard UDP port for Trap-PDUs

## RFC 1157 generic-trap definitions

sub COLD_START()             { 0 }  # coldStart(0)
sub WARM_START()             { 1 }  # warmStart(1)
sub LINK_DOWN()              { 2 }  # linkDown(2)
sub LINK_UP()                { 3 }  # linkUp(3)
sub AUTHENTICATION_FAILURE() { 4 }  # authenticationFailure(4)
sub EGP_NEIGHBOR_LOSS()      { 5 }  # egpNeighborLoss(5)
sub ENTERPRISE_SPECIFIC()    { 6 }  # enterpriseSpecific(6)

## Default, minimum, and maximum values 

sub DEFAULT_HOSTNAME()    { 'localhost' }
sub DEFAULT_COMMUNITY()   {    'public' }

sub DEFAULT_MTU()         {   484 } # RFC 1157 maximum size in octets
sub DEFAULT_TIMEOUT()     {   5.0 } # Timeout period for UDP in seconds
sub DEFAULT_RETRIES()     {     1 } # Number of retransmissions 

sub MINIMUM_MTU()         {    30 }    
sub MINIMUM_TIMEOUT()     {   1.0 }   
sub MINIMUM_RETRIES()     {     0 }     

sub MAXIMUM_MTU()         { 65535 }
sub MAXIMUM_TIMEOUT()     {  60.0 }   
sub MAXIMUM_RETRIES()     {    20 }

sub TRUE()                  { 0x1 }
sub FALSE()                 { 0x0 }

## Intialize global variables

sub BEGIN
{
   # Import symbol generating function
   use Symbol qw(gensym);

   $DEBUG      = FALSE;
   $FSM        = undef;
   $REQUEST_ID = int(rand 0xff) + (time() & 0xff);
   $SOCKET     = gensym();
}

# [public methods] -----------------------------------------------------------

sub new
{
   my ($class, %argv) = @_;

   # Create a new data structure for the object
   my $this = bless {
        '_buffer',        =>  "\0" x DEFAULT_MTU,
        '_callback'       =>  undef,
        '_community'      =>  DEFAULT_COMMUNITY,
        '_error'          =>  undef,
        '_error_index'    =>  0,
        '_error_status'   =>  0,
        '_fsm'            =>  undef,
        '_hostname'       =>  DEFAULT_HOSTNAME,
        '_leading_dot'    =>  FALSE,
        '_mtu'            =>  DEFAULT_MTU,
        '_nonblocking'    =>  FALSE,
        '_port'           =>  SNMP_PORT,
        '_request_id'     =>  $REQUEST_ID++,
        '_retries'        =>  DEFAULT_RETRIES,
        '_sockaddr'       =>  undef,
        '_socket'         =>  undef,
        '_type'           =>  undef,
        '_timeout'        =>  DEFAULT_TIMEOUT,
        '_translate'      =>  TRUE,
        '_var_bind_list'  =>  undef,
        '_version'        =>  SNMP_VERSION_1
   }, $class;

   # Validate the passed arguments
   foreach (keys %argv) {
      if (/^-?community$/i) {
         if ($argv{$_} eq '') {
            $this->_object_error('Empty community specified');
         } else {
            $this->{'_community'} = $argv{$_};
         }
      } elsif (/^-?debug$/i) {
         $this->debug($argv{$_});
      } elsif (/^-?hostname$/i) {
         if ($argv{$_} eq '') {
            $this->_object_error('Empty hostname specified');
         } else {
            $this->{'_hostname'} = $argv{$_};
         }
      } elsif (/^-?mtu$/i) {
         $this->mtu($argv{$_});
      } elsif (/^-?nonblocking$/i) {
         if ($argv{$_}) {
            $this->{'_nonblocking'} = TRUE;
         } else {
            $this->{'_nonblocking'} = FALSE;
         }
      } elsif (/^-?port$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            $this->_object_error('Expected numeric port number');
         } else {
            $this->{'_port'} = $argv{$_};
         }
      } elsif (/^-?retries$/i) {
         $this->retries($argv{$_});
      } elsif (/^-?timeout$/i) {
         $this->timeout($argv{$_});
      } elsif (/^-?translate$/i) {
         $this->translate($argv{$_});
      } elsif (/^-?version$/i) {
         $this->version($argv{$_});
      } else {
         $this->_object_error("Invalid argument '%s'", $_);
      }
      if (defined($this->{'_error'})) {
         return wantarray ? (undef, $this->{'_error'}) : undef;
      }
   }

   # Create a global Net:SNMP::FSM object (if not created) when the 
   # object has the "non-blocking" flag set, and store a reference
   # to the Finite State Machine (FSM) within the object. 

   if ($this->{'_nonblocking'}) {
      if ((!defined($FSM)) || (ref($FSM) ne 'Net::SNMP::FSM')) {
         if (!defined($FSM = Net::SNMP::FSM->new())) {
            # You should never see this error!
            $this->_object_error(
               "Unable to create Net::SNMP Finite State Machine object"
            );
            return wantarray ? (undef, $this->{'_error'}) : undef;
         }
      }
      $this->{'_fsm'} = $FSM;
   }

   # Return the object and empty error message (in list context)
   wantarray ? ($this, '') : $this;
}

sub open 
{
   my $this = shift;
   my ($host_addr, $proto) = (undef, undef);
   
   # Resolve the hostname to an IP address
   if (!defined($host_addr = inet_aton($this->hostname))) {
      return $this->_object_error(
         "Unable to resolve hostname '%s'", $this->hostname
      );
   }

   # Pack the address and port information
   $this->{'_sockaddr'} = sockaddr_in($this->{'_port'}, $host_addr);

   # Get the protocol number for UDP
   if (!defined($proto = scalar(getprotobyname('udp')))) { 
      $proto = IPPROTO_UDP;
   } 

   # Open a global UDP socket for the package (if not open), and store
   # a reference to the socket within the object.
 
   if (!fileno($SOCKET)) {
      if (!socket($SOCKET, PF_INET, SOCK_DGRAM, $proto)) {
         return $this->_object_error("socket(): %s", $!);
      }
   }
   $this->{'_socket'} = $SOCKET;

   TRUE;
}

sub close
{
   my $this = shift;
   
   # Clear all of the buffers and errors
   $this->_object_clear_buffer;
   $this->_object_clear_var_bind_list;
   $this->_object_clear_error;

   # Clear the socket refence so that we can tell that this particular
   # object has been closed.

   $this->{'_socket'} = undef;

   TRUE;
}

sub session
{
   my $class = shift;

   # This is a convenience method (also present for backwards compatiblity),
   # that just combines the new() and the open() methods into one method.

   my ($this, $error) = $class->new(@_);

   if (defined($this)) { 
      if (!defined($this->open)) { 
         return wantarray ? (undef, $this->error) : undef; 
      }
   }

   wantarray ? ($this, $error) : $this; 
}

sub snmp_event_loop 
{ 
   ref($FSM) eq 'Net::SNMP::FSM' ? $FSM->event_loop : undef;
}

sub snmp_one_event
{ 
   ref($FSM) eq 'Net::SNMP::FSM' ? $FSM->one_event : undef;
}

sub get_request
{
   my $this = shift;

   # Handle passed arguments according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      my %argv = @_;
      my @var_bind_list = ();

      # Validate the passed arguments
      foreach (keys %argv) {
         if (/^-?callback$/i) {
            if (!defined($this->_object_add_callback($argv{$_}))) {
               return $this->_object_error;
            }
         } elsif (/^-?varbindlist$/i) {
            if (ref($argv{$_}) ne 'ARRAY') {
               return $this->_object_error(
                  'Expected array reference for variable-bindings'
               );
            } else {
               @var_bind_list = @{$argv{$_}};
            }
         } else {
            return $this->_object_error("Invalid argument '%s'", $_);
         }
      }

      # Encode and queue the message
      if (!defined($this->_snmp_encode_get_request(@var_bind_list))) {
         return $this->_object_encode_error;
      }
      $this->_object_queue_message;

   } else {

      # Encode, send and wait, and decode the message
      if (!defined($this->_snmp_encode_get_request(@_))) { 
         return $this->_object_encode_error; 
      }
      if (!defined($this->_udp_send_message)) { 
         return $this->_object_decode_error; 
      }
      $this->_snmp_validate_get_response; 

   }
}

sub get_next_request
{
   my $this = shift;

   # Handle passed arguments according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      my %argv = @_;
      my @var_bind_list = ();

      # Validate the passed arguments
      foreach (keys %argv) {
         if (/^-?callback$/i) {
            if (!defined($this->_object_add_callback($argv{$_}))) {
               return $this->_object_error;
            }
         } elsif (/^-?varbindlist$/i) {
            if (ref($argv{$_}) ne 'ARRAY') {
               return $this->_object_error(
                  'Expected array reference for variable-bindings'
               );
            } else {
               @var_bind_list = @{$argv{$_}};
            }
         } else {
            return $this->_object_error("Invalid argument '%s'", $_);
         }
      }

      # Encode and queue the message
      if (!defined($this->_snmp_encode_get_next_request(@var_bind_list))) {
         return $this->_object_encode_error;
      }
      $this->_object_queue_message;

   } else {

      # Encode, send and wait, and decode the message
      if (!defined($this->_snmp_encode_get_next_request(@_))) {
         return $this->_object_encode_error;
      }
      if (!defined($this->_udp_send_message)) {
         return $this->_object_decode_error;
      }
      $this->_snmp_validate_get_response;

   }
}

sub set_request
{
   my $this = shift;

   # Handle passed arguments according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      my %argv = @_;
      my @var_bind_list = ();

      # Validate the passed arguments
      foreach (keys %argv) {
         if (/^-?callback$/i) {
            if (!defined($this->_object_add_callback($argv{$_}))) {
               return $this->_object_error;
            }
         } elsif (/^-?varbindlist$/i) {
            if (ref($argv{$_}) ne 'ARRAY') {
               return $this->_object_error(
                  'Expected array reference for variable-bindings'
               );
            } else {
               @var_bind_list = @{$argv{$_}};
            }
         } else {
            return $this->_object_error("Invalid argument '%s'", $_);
         }
      }

      # Encode and queue the message
      if (!defined($this->_snmp_encode_set_request(@var_bind_list))) {
         return $this->_object_encode_error;
      }
      $this->_object_queue_message;

   } else {

      # Encode, send and wait, and decode the message
      if (!defined($this->_snmp_encode_set_request(@_))) {
         return $this->_object_encode_error;
      }
      if (!defined($this->_udp_send_message)) {
         return $this->_object_decode_error;
      }
      $this->_snmp_validate_get_response;

   }
}

sub trap
{
   my ($this, %argv) = @_;

   # Clear any previous error message
   $this->_object_clear_error;

   # Use Sys:Hostname to determine the IP address of the client sending
   # the trap.  Only require the module for Trap-PDUs.

   use Sys::Hostname();

   # Setup default values for the Trap-PDU by creating new entries in 
   # the Net::SNMP object.

   # Use iso.org.dod.internet.private.enterprises for the default enterprise. 
   $this->{'_enterprise'} = '1.3.6.1.4.1';  

   # Create an entry for the agent-addr (we will fill it in below if
   # if the user does not specify an address)
   $this->{'_agent_addr'} = undef;

   # Use enterpriseSpecific(6) for the generic-trap type.
   $this->{'_generic_trap'} = ENTERPRISE_SPECIFIC;

   # Set the specific-trap type to 0.
   $this->{'_specific_trap'} = 0;

   # Use the "uptime" of the script for the time-stamp.
   $this->{'_time_stamp'} = ((time() - $^T) * 100);

   # Create a local copy of the VarBindList.
   my @var_bind_list = ();


   # Validate the passed arguments
   foreach (keys %argv) {
      if (/^-?enterprise$/i) {
         if ($argv{$_} !~ /^\.?\d+\.\d+(\.\d+)*/) {
            return $this->_object_error(
               'Expected enterprise as OBJECT IDENTIFIER in dotted notation' 
            );
         } else {
            $this->{'_enterprise'} = $argv{$_};
         }
      } elsif (/^-?agentaddr$/i) {
         if ($argv{$_} !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
            return $this->_object_error(
               'Expected agent-addr in dotted notation'     
            );
         } else {
            $this->{'_agent_addr'} = $argv{$_};
         }
      } elsif (/^-?generictrap$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            return $this->_object_error(
               'Expected numeric generic-trap type'
            );
         } else {
            $this->{'_generic_trap'} = $argv{$_};
         }
      } elsif (/^-?specifictrap$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            return $this->_object_error(
               'Expected numeric specific-trap type'
            );
         } else {
            $this->{'_specific_trap'} = $argv{$_};
         }
      } elsif (/^-?timestamp$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            return $this->_object_error('Expected numeric time-stamp');
         } else {
            $this->{'_time_stamp'} = $argv{$_};
         }
      } elsif (/^-?varbindlist$/i) {
         if (ref($argv{$_}) ne 'ARRAY') {
            return $this->_object_error(
               'Expected array reference for variable-bindings'
            );
         } else {
            @var_bind_list = @{$argv{$_}}; 
         }   
      } else {
         return $this->_object_error("Invalid argument '%s'", $_);
      }
   }

   # If the user did not specify the agent-addr, get local the address 
   # of the client sending the trap. 

   if (!defined($this->{'_agent_addr'})) {
      eval {
         $this->{'_agent_addr'} = inet_ntoa(
            scalar(gethostbyname(&Sys::Hostname::hostname()))
         );
      };
      if ($@ ne '') {
         return $this->_object_encode_error(
            'Unable to resolve local agent-addr'
         );
      }
   }

   if (!defined($this->_snmp_encode_trap(@var_bind_list))) {
      return $this->_object_encode_error;
   }

   # Handle the sending of the message according to "blocking" mode

   if ($this->{'_nonblocking'}) {
      $this->_object_queue_message;
   } else {
      $this->_udp_send_buffer;
   }
}

sub get_bulk_request
{
   my ($this, %argv) = @_;

   # Clear any previous error message which also clears the 
   # '_error_status' and '_error_index' so that we can use them 
   # to hold non-repeaters and max-repetitions.  
  
   $this->_object_clear_error;

   # Validate the SNMP version
   if ($this->version != SNMP_VERSION_2C) {
      return $this->_object_error(
         'GetBulkRequest-PDU only supported with SNMPv2c'
      );
   }

   # Create a local copy of the VarBindList.
   my @var_bind_list = ();

   # Validate the passed arguments
   foreach (keys %argv) {
      if (/^-?nonrepeaters$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            return $this->_object_error(
               'Expected numeric non-repeaters value'
            );
         } elsif ($argv{$_} > 2147483647) {
            return $this->_object_error(
               'Exceeded maximum non-repeaters value [2147483647]'
            );
         } else {
            # We cheat a little here...
            $this->{'_error_status'} = $argv{$_};
         }
      } elsif (/^-?maxrepetitions$/i) {
         if ($argv{$_} !~ /^\d+$/) {
            return $this->_object_error(
               'Expected numeric max-repetitions value'
            );
         } elsif ($argv{$_} > 2147483647) {
            return $this->_object_error(
               'Exceeded maximum max-repetitions value [2147483647]'
            );
         } else {
            # We cheat a little here...
            $this->{'_error_index'} = $argv{$_};
         }
      } elsif (/^-?varbindlist$/i) {
         if (ref($argv{$_}) ne 'ARRAY') {
            return $this->_object_error(
               'Expected array reference for variable-bindings'
            );
         } else {
            @var_bind_list = @{$argv{$_}};
         }
      } elsif ((/^-?callback$/i) && ($this->{'_nonblocking'})) {
         if (!defined($this->_object_add_callback($argv{$_}))) {
            return $this->_object_error;
         }
      } else {
         return $this->_object_error("Invalid argument '%s'", $_);
      }
   }

   if ($this->{'_error_status'} > scalar(@var_bind_list)) {
      return $this->_object_error(
         'Non-repeaters greater than the number of variable-bindings'
      );
   }

   if (($this->{'_error_status'} == scalar(@var_bind_list)) &&
       ($this->{'_error_index'} != 0))
   {
      return $this->_object_error(
         'Non-repeaters equals the number of variable-bindings and ' .
         'max-repetitions is not equal to zero'
      );
   }

   if (!defined($this->_snmp_encode_get_bulk_request(@var_bind_list))) {
      return $this->_object_encode_error;
   }

   # Handle the sending of the message according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      $this->_object_queue_message;

   } else {

      if (!defined($this->_udp_send_message)) {
         return $this->_object_decode_error;
      }
      $this->_snmp_validate_get_response;

   }
}

sub inform_request
{
   my $this = shift;
  
   # Clear any previous error message
   $this->_object_clear_error;

   # Validate the SNMP version
   if ($this->version != SNMP_VERSION_2C) {
      return $this->_object_error(
         'InformRequest-PDU only supported with SNMPv2c'
      );
   }
 
   # Handle passed arguments according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      my %argv = @_;
      my @var_bind_list = ();

      # Validate the passed arguments
      foreach (keys %argv) {
         if (/^-?callback$/i) {
            if (!defined($this->_object_add_callback($argv{$_}))) {
               return $this->_object_error;
            }
         } elsif (/^-?varbindlist$/i) {
            if (ref($argv{$_}) ne 'ARRAY') {
               return $this->_object_error(
                  'Expected array reference for variable-bindings'
               );
            } else {
               @var_bind_list = @{$argv{$_}};
            }
         } else {
            return $this->_object_error("Invalid argument '%s'", $_);
         }
      }

      # Encode and queue the message
      if (!defined($this->_snmp_encode_inform_request(@var_bind_list))) {
         return $this->_object_error;
      }
      $this->_object_queue_message;

   } else {

      # Encode, send/wait, and decode the message
      if (!defined($this->_snmp_encode_inform_request(@_))) {
         return $this->_object_encode_error;
      }
      if (!defined($this->_udp_send_message)) {
         return $this->_object_decode_error;
      }
      $this->_snmp_validate_get_response;

   }
}

sub snmpv2_trap 
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Validate the SNMP version
   if ($this->version != SNMP_VERSION_2C) {
      return $this->_object_error(
         'SNMPv2-Trap-PDU only supported with SNMPv2c'
      );
   }

   # Encode the message
   if (!defined($this->_snmp_encode_v2_trap(@_))) {
      return $this->_object_encode_error;
   }

   if ($this->{'_nonblocking'}) {
      # Queue the message
      $this->_object_queue_message;
   } else {
      # Send the message
      $this->_udp_send_buffer;
   }
}

sub get_table
{
   my $this = shift;
   my ($base_oid, $repeat_cnt, $table) = (undef, 0, undef, undef);
   my ($result, $next_oid) = (undef, undef);

   # Use get-next-requests until the response is not a subtree of the
   # base OBJECT IDENTIFIER.  Return the table only if there are no
   # errors other than a noSuchName(2) error since the table could
   # be at the end of the tree.  Also return the table when the value
   # of the OID equals endOfMibView(2) when using SNMPv2c.

   # Handle passed argument according to "blocking" mode

   if ($this->{'_nonblocking'}) {

      my %argv = @_;

      # Validate the passed arguments
      foreach (keys %argv) {
         if (/^-?callback$/i) {
            if (!defined($this->_object_add_callback($argv{$_}))) {
               return $this->_object_error;
            }
         } elsif (/^-?baseoid$/i) {
            if ($argv{$_} !~ /^\.?\d+\.\d+(\.\d+)*/) {
               return $this->_object_error(
                  'Expected base OBJECT IDENTIFIER in dotted notation'
               );
            } else {
               $base_oid = $argv{$_};
            }
         } else {
            return $this->_object_error("Invalid argument '%s'", $_);
         }
      }

      # Create table of values that need passed along with the
      # callbacks.  This just prevents a big argument list.

      %argv = (
         'base_oid'   => $base_oid,
         'callback'   => $this->{'_callback'},
         'repeat_cnt' => 0,
         'table'      => undef,
      );

      # Queue up the get-next-request, overriding the user-specified
      # callback.  We have the original in the arguments. 

      $this->get_next_request(
         -callback    => [\&_object_get_table_cb, \%argv],
         -varbindlist => [$base_oid]
      );

   } else {

      $next_oid = $base_oid = shift(@_);

      do {
         if (defined($result)) {
            if (!exists($table->{$next_oid})) {
               $table->{$next_oid} = $result->{$next_oid};
            } elsif ((($result->{$next_oid} eq 'endOfMibView') ||  # translate
                     ($this->error_status == ENDOFMIBVIEW)) &&     # !translate 
                     ($this->version == SNMP_VERSION_2C))
            {
               if (!defined($table)) {
                  $this->_object_decode_error('Requested table is empty');
               }
               return ($this->{'_var_bind_list'} = $table);
            } else {
               $repeat_cnt++;
            }
         }
         # Check to make sure that the remote host does not respond
         # incorrectly causing the get-next-requests to loop forever.
         if ($repeat_cnt > 5) {
            return $this->_object_decode_error(
               'Loop detected with table on remote host'
            );
         }
         if (!defined($result = $this->get_next_request($next_oid))) {
            # Check for noSuchName(2) error 
            if ($this->error_status == 2) { 
               if (!defined($table)) {
                  $this->_object_decode_error('Requested table is empty');
               }   
               return ($this->{'_var_bind_list'} = $table);
            } else {
               return $this->_object_decode_error;
            }
         }
         ($next_oid) = keys(%{$result});
      } while (_asn1_oid_context_match($base_oid, $next_oid));

      if (!defined($table)) {
         $this->_object_decode_error('Requested table is empty');
      }

      $this->{'_var_bind_list'} = $table;
   }
}

sub error 
{ 
   defined($_[0]->{'_error'}) ? $_[0]->{'_error'} : ''; 
}

sub version
{
   my ($this, $version) = @_;

   # Clear any previous error message
   $this->_object_clear_error;

   # Allow the user some flexability
   my $supported = {
      '1'       => SNMP_VERSION_1,
      'v1'      => SNMP_VERSION_1,
      'snmpv1'  => SNMP_VERSION_1,
      '2'       => SNMP_VERSION_2C,
      '2c'      => SNMP_VERSION_2C,
      'v2'      => SNMP_VERSION_2C,
      'v2c'     => SNMP_VERSION_2C,
      'snmpv2'  => SNMP_VERSION_2C,
      'snmpv2c' => SNMP_VERSION_2C
   };

   if (@_ == 2) {
      if (exists($supported->{lc($version)})) {
         $this->{'_version'} = $supported->{lc($version)};
      } else {
         return $this->_object_error(
            "Unknown or invalid SNMP version [%s]", $version
         );
      }
   }

   $this->{'_version'};
}

sub hostname      
{ 
   $_[0]->{'_hostname'};      
} 

sub _request_id   
{ 
   $_[0]->{'_request_id'};  # Semi-private method   
} 

sub error_status  
{ 
   $_[0]->{'_error_status'};  
}

sub error_index   
{ 
   $_[0]->{'_error_index'};   
}

sub var_bind_list 
{ 
   $_[0]->{'_var_bind_list'}; 
}

sub timeout
{
   my ($this, $timeout) = @_;

   # Clear any previous error message
   $this->_object_clear_error;

   if (@_ == 2) {
      if (($timeout >= MINIMUM_TIMEOUT) && ($timeout <= MAXIMUM_TIMEOUT)) { 
         $this->{'_timeout'} = $timeout; 
      } else {
         return $this->_object_encode_error(
            "Timeout out of range [%03.01f - %03.01f seconds]",
            MINIMUM_TIMEOUT, MAXIMUM_TIMEOUT
         );
      } 
   }

   $this->{'_timeout'};
}

sub retries 
{
   my ($this, $retries) = @_;

   # Clear any previous error message
   $this->_object_clear_error;

   if (@_ == 2) {
      if (($retries >= MINIMUM_RETRIES) && ($retries <= MAXIMUM_RETRIES)) { 
         $this->{'_retries'} = $retries; 
      } else {
         return $this->_object_encode_error(
            "Retries out of range [%d - %d]", MINIMUM_RETRIES, MAXIMUM_RETRIES
         );
      }
   }

   $this->{'_retries'};
}

sub mtu 
{
   my ($this, $mtu) = @_;

   # Clear any previous error message
   $this->_object_clear_error;

   if (@_ == 2) {
      if (($mtu >= MINIMUM_MTU) && ($mtu <= MAXIMUM_MTU )) { 
         $this->{'_mtu'} = $mtu; 
      } else {
         return $this->_object_encode_error(
            "MTU out of range [%d - %d octets]", MINIMUM_MTU, MAXIMUM_MTU
         );
      }
   }

   $this->{'_mtu'};
}

sub translate
{
   my ($this, $flag) = @_;
 
   if (@_ == 2) {
      if ($flag) {
         $this->{'_translate'} = TRUE;
      } else {
         $this->{'_translate'} = FALSE;
      }
   }

   $this->{'_translate'};
}

sub debug
{
   my ($this, $flag) = @_;

   if (@_ == 2) {
      if ($flag) {
         $Net::SNMP::DEBUG = TRUE;
      } else {
         $Net::SNMP::DEBUG = FALSE;
      }
   }

   $Net::SNMP::DEBUG;  
}

sub END 
{
   if (defined($SOCKET)) { 
      if (fileno($SOCKET)) { CORE::close($SOCKET); } 
   }
}


## Utility functions

sub snmp_debug($)         
{ 
   &Net::SNMP::debug(undef, $_[0]);         
}

sub oid_context_match($$) 
{ 
   &Net::SNMP::_asn1_oid_context_match($_[0], $_[1]); 
}

sub oid_lex_sort(@)       
{ 
   &Net::SNMP::_asn1_oid_lex_sort(@_);      
}

sub ticks_to_time($)      
{ 
   &Net::SNMP::_asn1_ticks_to_time($_[0]);  
}


# [private methods] ----------------------------------------------------------


###
## Simple Network Managment Protocol (SNMP) encode methods
###

sub _snmp_encode_get_request
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Check for a valid VarBindList
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); }  

   $this->_snmp_encode(GET_REQUEST, $this->_snmp_create_oid_null_pairs(@_));
}

sub _snmp_encode_get_next_request
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Check for a valid VarBindList
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); }

   $this->_snmp_encode(
      GET_NEXT_REQUEST, $this->_snmp_create_oid_null_pairs(@_)
   );
}

sub _snmp_encode_get_response
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   $this->_snmp_encode_error('GetResponse-PDU not supported');
}


sub _snmp_encode_set_request
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Check for a valid VarBindList
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); }

   $this->_snmp_encode(SET_REQUEST, $this->_snmp_create_oid_value_pairs(@_));
}

sub _snmp_encode_trap
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   $this->_snmp_encode(TRAP, $this->_snmp_create_oid_value_pairs(@_));
}

sub _snmp_encode_get_bulk_request
{
   my $this = shift;

   # DO NOT clear any previous error message or the non-repeaters
   # and the max-repetitions will be reset!

   # Check for a valid VarBindList
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); } 

   $this->_snmp_encode(
      GET_BULK_REQUEST, $this->_snmp_create_oid_null_pairs(@_)
   );
}

sub _snmp_encode_inform_request
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Check for a valid VarBindList
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); }

   $this->_snmp_encode(INFORM_REQUEST, $this->_snmp_create_oid_value_pairs(@_));
}

sub _snmp_encode_v2_trap
{
   my $this = shift;

   # Clear any previous error message
   $this->_object_clear_error;

   # Check for a valid VarBindList (or is that legal with a snmpV2-trap?)
   if (@_ < 1) { return $this->_snmp_encode_error('VarBindList is empty'); }

   $this->_snmp_encode(SNMPV2_TRAP, $this->_snmp_create_oid_value_pairs(@_));
} 

sub _snmp_encode
{
   my ($this, $type, @var_bind_list) = @_;
   
   # Do not do anything if there has already been an error
   if (defined($this->{'_error'})) { return $this->_snmp_encode_error; }

   # We need to reset the buffer that might have been defined
   # from a previous message and clear the var_bind_list.

   $this->_object_clear_buffer;
   $this->_object_clear_var_bind_list;
   $this->_object_clear_leading_dot;

   # Make sure the PDU type was passed
   if ((scalar(@_) < 2) || (!defined($type))) {
      return $this->_snmp_encode_error('No SNMP PDU type defined');
   } 
   $this->{'_type'} = $type;
    
   # Encode the variable-bindings
   if (!defined($this->_snmp_encode_var_bind_list(@var_bind_list))) {
      return $this->_snmp_encode_error;
   }

   # Encode the PDU or Trap-PDU
   if ($this->{'_type'} == TRAP) {
      if (!defined($this->_snmp_encode_trap_pdu)) {
         return $this->_snmp_encode_error;
      }
   } else {
      if (!defined($this->_snmp_encode_pdu)) {
         return $this->_snmp_encode_error;
      }
   } 

   # Now wrap the message
   $this->_snmp_encode_message;
}

sub _snmp_encode_message
{
   my $this = shift;

   # We need to encode eveything in reverse order so the
   # objects end up in the correct place.

   # Encode the PDU type
   if (!defined(
         $this->_asn1_encode($this->{'_type'}, $this->_object_get_buffer)
      )) 
   { 
      return $this->_snmp_encode_error; 
   }

   # Encode the community name
   if (!defined($this->_asn1_encode(OCTET_STRING, $this->{'_community'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the SNMP version
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_version'}))) {
      return $this->_snmp_encode_error;
   } 

   # Encode the SNMP message SEQUENCE 
   if (!defined($this->_asn1_encode(SEQUENCE, $this->_object_get_buffer))) {
      return $this->_snmp_encode_error;
   } 

   # Return the buffer
   $this->{'_buffer'};
}
 
sub _snmp_encode_pdu
{
   my $this = shift;

   # We need to encode eveything in reverse order so the 
   # objects end up in the correct place.

   # Encode the error-index or max-repetitions (GetBulkRequest-PDU)  
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_error_index'}))) {
      return $this->_snmp_encode_error;
   } 
   
   # Encode the error-status or non-repeaters (GetBulkRequest-PDU) 
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_error_status'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the request-id after storing the value and incrementing 
   # the global value by one.

   $this->{'_request_id'} = $REQUEST_ID++;
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_request_id'}))) {
      return $this->_snmp_encode_error;
   }
 
   # Return the buffer 
   $this->{'_buffer'}; 
}

sub _snmp_encode_trap_pdu
{
   my $this = shift;

   # We need to encode eveything in reverse order so the
   # objects end up in the correct place.

   # Encode the time-stamp
   if (!defined($this->_asn1_encode(TIMETICKS, $this->{'_time_stamp'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the specific-trap type
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_specific_trap'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the generic-trap type
   if (!defined($this->_asn1_encode(INTEGER, $this->{'_generic_trap'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the agent-addr
   if (!defined($this->_asn1_encode(IPADDRESS, $this->{'_agent_addr'}))) {
      return $this->_snmp_encode_error;
   }

   # Encode the enterprise
   if (!defined(
         $this->_asn1_encode(OBJECT_IDENTIFIER, $this->{'_enterprise'})
      )) 
   { 
      return $this->_snmp_encode_error;
   }

   # Return the buffer
   $this->{'_buffer'};
}

sub _snmp_encode_var_bind_list
{
   my ($this, @var_bind) = @_;
   my ($type, $value) = (undef, '');

   # The passed array is expected to consist of groups of four values
   # consisting of two sets of ASN.1 types and their values.

   if ((scalar(@var_bind) % 4)) {
      return $this->_snmp_encode_error(
         "Invalid number of VarBind parameters [%d]", scalar(@var_bind) 
      );
   }
 
   # Encode the objects from the end of the list, so they are wrapped 
   # into the packet as expected.  Also, check to make sure that the 
   # OBJECT IDENTIFIER is in the correct place.

   my $buffer = $this->_object_get_buffer;

   while (@var_bind) {
      # Encode the ObjectSyntax
      $value = pop(@var_bind);
      $type  = pop(@var_bind);
      if (!defined($this->_asn1_encode($type, $value))) { 
         return $this->_snmp_encode_error; 
      }
      # Encode the ObjectName 
      $value = pop(@var_bind);
      $type  = pop(@var_bind);
      if ($type != OBJECT_IDENTIFIER) {
         return $this->_snmp_encode_error(
            'Expected OBJECT IDENTIFIER in VarBindList'
         );
      }
      if (!defined($this->_asn1_encode($type, $value))) {
         return $this->_snmp_encode_error;
      }
      # Encode the VarBind SEQUENCE 
      if (!defined($this->_asn1_encode(SEQUENCE, $this->_object_get_buffer))) {
         return $this->_snmp_encode_error;
      } 
      $buffer = join('', $this->_object_get_buffer, $buffer);
   } 

   # Encode the VarBindList SEQUENCE 
   if (!defined($this->_asn1_encode(SEQUENCE, $buffer))) { 
      return $this->_snmp_encode_error; 
   }

   # Return the buffer
   $this->{'_buffer'};
}

sub _snmp_create_oid_null_pairs
{
   my ($this, @oids) = @_;
   my ($oid) = (undef);
   my @pairs = ();

   while (@oids) {
      $oid = shift(@oids);
      if ($oid !~ /^\.?\d+\.\d+(\.\d+)*/) {
         return $this->_snmp_encode_error(
            'Expected OBJECT IDENTIFIER in dotted notation'
         );
      }
      push(@pairs, OBJECT_IDENTIFIER, $oid, NULL, '');
   }

   @pairs;
}

sub _snmp_create_oid_value_pairs
{
   my ($this, @oid_values) = @_;
   my ($oid) = (undef);
   my @pairs = ();

   if ((scalar(@oid_values) % 3)) {
      return $this->_snmp_encode_error(
         'Expected [OBJECT IDENTIFIER, ASN.1 type, object value] combination'
      );
   }

   while (@oid_values) {
      $oid = shift(@oid_values);
      if ($oid !~ /^\.?\d+\.\d+(\.\d+)*/) {
         return $this->_snmp_encode_error(
            'Expected OBJECT IDENTIFIER in dotted notation'
         );
      }
      push(@pairs, OBJECT_IDENTIFIER, $oid);
      push(@pairs, shift(@oid_values), shift(@oid_values));
   }

   @pairs;
}

sub _snmp_encode_error
{
   my $this = shift;

   # Clear the buffer
   $this->_object_clear_buffer;

   $this->_object_error(@_);
}


###
## Simple Network Managment Protocol (SNMP) decode methods
###

sub _snmp_decode_get_request      
{ 
   $_[0]->_snmp_decode(GET_REQUEST);      
}

sub _snmp_decode_get_next_request 
{ 
   $_[0]->_snmp_decode(GET_NEXT_REQUEST); 
}

sub _snmp_decode_get_response     
{ 
   $_[0]->_snmp_decode(GET_RESPONSE);     
}

sub _snmp_decode_set_request      
{ 
   $_[0]->_snmp_decode(SET_REQUEST);      
}

sub _snmp_decode_trap             
{ 
   $_[0]->_snmp_decode(TRAP);             
}

sub _snmp_decode_get_bulk_request 
{ 
   $_[0]->_snmp_decode(GET_BULK_REQUEST); 
}

sub _snmp_decode_inform_request   
{ 
   $_[0]->_snmp_decode(INFORM_REQUEST);   
} 

sub _snmp_decode_v2_trap          
{ 
   $_[0]->_snmp_decode(SNMPV2_TRAP);      
} 

sub _snmp_decode
{
   my ($this, $type) = @_;

   # First we need to reset the var_bind_list and errors that
   # might have been set from a previous message.

   $this->_object_clear_snmp_message;
   $this->_object_clear_var_bind_list;
   $this->_object_clear_error;

   # Define the message type to be decoded, if it was provided
   if (scalar(@_) > 1) { $this->{'_type'} = $type; } 

   # Decode the message
   if (!defined($this->_snmp_decode_message)) {
      return $this->_snmp_decode_error;
   }

   # Decode the PDU
   if ($this->{'_type'} != TRAP) {
      if (!defined($this->_snmp_decode_pdu)) { 
         return $this->_snmp_decode_error;
      }
   } else {
      if (!defined($this->_snmp_decode_trap_pdu)) { 
         return $this->_snmp_decode_error;
      }
   }

   # Decode the VarBindList
   $this->_snmp_decode_var_bind_list;
}

sub _snmp_decode_message
{
   my $this = shift;
   my $value = undef;

   # Decode the message SEQUENCE
   if (!defined($value = $this->_asn1_decode(SEQUENCE))) {
      return $this->_snmp_decode_error;
   } 
   if ($value != $this->_object_buffer_length) {
      return $this->_snmp_decode_error(
         'Encoded message length not equal to remaining data length' 
      );
   }

   # Decode the version
   if (!defined($this->{'_version'} = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   } 

   # Decode the community
   if (!defined($this->{'_community'} = $this->_asn1_decode(OCTET_STRING))) {
      return $this->_snmp_decode_error;
   }

   # Decode the PDU type
   if (!defined($this->{'_type'} = $this->_asn1_decode($this->{'_type'}))) {
      return $this->_snmp_decode_error;
   }

   # Return the remaining buffer
   $this->{'_buffer'};
}

sub _snmp_decode_pdu
{
   my $this = shift;

   # Decode the request-id
   if (!defined($this->{'_request_id'} = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   }

   # Decode the error-status and error-index 
   if (!defined($this->{'_error_status'} = $this->_asn1_decode(INTEGER))) {
      $this->{'_error_status'} = 0;
      return $this->_snmp_decode_error;
   }
   if (!defined($this->{'_error_index'} = $this->_asn1_decode(INTEGER))) {
      $this->{'_error_index'} = 0;
      return $this->_snmp_decode_error;
   }

   # Return the remaining buffer 
   $this->{'_buffer'};
}

sub _snmp_decode_trap_pdu
{
   my $this = shift;

   # Decode the enterprise 
   if (!defined(
         $this->{'_enterprise'} = $this->_asn1_decode(OBJECT_IDENTIFIER)
      )) 
   {
      return $this->_snmp_decode_error;
   }

   # Decode the agent-addr
   if (!defined($this->{'_agent_addr'} = $this->_asn1_decode(IPADDRESS))) {
      return $this->_snmp_decode_error;
   }

   # Decode the generic-trap type
   if (!defined($this->{'_generic_trap'} = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   }

   # Decode the specific-trap type
   if (!defined($this->{'_specific_trap'} = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   }

   # Decode the time-stamp
   if (!defined($this->{'_time_stamp'} = $this->_asn1_decode(TIMETICKS))) {
      return $this->_snmp_decode_error;
   }

   # Return the remaining buffer
   $this->{'_buffer'};
}

sub _snmp_decode_var_bind_list
{
   my $this = shift;
   my ($value, $oid) = (undef, undef);

   # Decode the VarBindList SEQUENCE
   if (!defined($value = $this->_asn1_decode(SEQUENCE))) {
      return $this->_snmp_decode_error;
   }
   if ($value != $this->_object_buffer_length) {
      return $this->_snmp_decode_error(
         'Encoded VarBindList length not equal to remaining data length' 
      );
   }

   $this->{'_var_bind_list'} = {};

   while ($this->_object_buffer_length) {
      # Decode the VarBind SEQUENCE
      if (!defined($this->_asn1_decode(SEQUENCE))) {
         return $this->_snmp_decode_error;
      }
      # Decode the ObjectName
      if (!defined($oid = $this->_asn1_decode(OBJECT_IDENTIFIER))) {
         return $this->_snmp_decode_error;
      }
      # Decode the ObjectSyntax
      if (!defined($value = $this->_asn1_decode)) {
         return $this->_snmp_decode_error;
      }
      # Create a hash consisting of the OBJECT IDENTIFIER as a
      # key and the ObjectSyntax as the value.
      DEBUG_INFO("{ %s => %s }", $oid, $value);
      $this->{'_var_bind_list'}->{$oid} = $value;
   }

   # Return the var_bind_list hash
   $this->{'_var_bind_list'};
}

sub _snmp_validate_get_response
{
   my $this = shift;
   my $value = undef;

   # First we need to reset the var_bind_list and errors that
   # might have been set from a previous message.

   $this->_object_clear_var_bind_list;
   $this->_object_clear_error;

   $this->{'_type'} = GET_RESPONSE;

   # Decode the message SEQUENCE
   if (!defined($value = $this->_asn1_decode(SEQUENCE))) {
      return $this->_snmp_decode_error;
   }
   if ($value != $this->_object_buffer_length) {
      return $this->_snmp_decode_error(
         'Encoded message length not equal to remaining data length'
      );
   }

   # Decode and validate the version
   if (!defined($value = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   }
   if ($value != $this->{'_version'}) {
      return $this->_snmp_decode_error(
         "Received version [0x%02x] is not equal to transmitted version " .
         "[0x%02x]", $value, $this->{'_version'}
      );
   }

   # Decode and validate the community
   if (!defined($value = $this->_asn1_decode(OCTET_STRING))) {
      return $this->_snmp_decode_error;
   }
   if ($value ne $this->{'_community'}) {
      return $this->_snmp_decode_error(
         "Received community [%s] is not equal to transmitted community [%s]",
         $value, $this->{'_community'}
      );
   }

   # Decode the PDU type
   if (!defined($this->_asn1_decode($this->{'_type'}))) {
      return $this->_snmp_decode_error;
   }

   # Decode the request-id
   if (!defined($value = $this->_asn1_decode(INTEGER))) {
      return $this->_snmp_decode_error;
   }
   if ($value != $this->{'_request_id'}) {
      return $this->_snmp_decode_error(
         "Received request-id [%s] is not equal to transmitted request-id [%s]",
         $value, $this->{'_request_id'}
      );
   }

   # Decode and validate the error-status and error-index
   if (!defined($this->{'_error_status'} = $this->_asn1_decode(INTEGER))) {
      $this->{'_error_status'} = 0;
      return $this->_snmp_decode_error;
   }
   if (!defined($this->{'_error_index'} = $this->_asn1_decode(INTEGER))) {
      $this->{'_error_index'} = 0;
      return $this->_snmp_decode_error;
   }
   if (($this->{'_error_status'} != 0) || ($this->{'_error_index'} != 0)) {
      return $this->_snmp_decode_error(
         "Received SNMP %s error-status at error-index %s",
         _snmp_error_status_itoa($this->{'_error_status'}), 
         $this->{'_error_index'}
      );
   }

   # Decode the VarBindList
   $this->_snmp_decode_var_bind_list;
}

sub _snmp_compare_get_response
{
   my ($this, $response) = @_;

   if (ref($response) ne ref($this)) {
      $this->_snmp_decode_error('Invalid object reference');
   }

   # Validate the version
   if ($this->{'_version'} != $response->{'_version'}) {
      return $this->_snmp_decode_error(
         "Received version [0x%02x] is not equal to transmitted version " .
         "[0x%02x]", $response->{'_version'}, $this->{'_version'}
      );
   }

   # Validate the community
   if ($this->{'_community'} ne $response->{'_community'}) {
      return $this->_snmp_decode_error(
         "Received community [%s] is not equal to transmitted community [%s]",
         $response->{'_community'}, $this->{'_community'}
      );
   }

   # Validate the request-id
   if ($this->{'_request_id'} != $response->{'_request_id'}) {
      return $this->_snmp_decode_error(
         "Received request-id [%s] is not equal to transmitted request-id [%s]",
         $response->{'_request_id'}, $this->{'_request_id'}
      );
   }

   # Validate the error-status and error-index field.
   if (($response->{'_error_status'} != 0) ||
       ($response->{'_error_index'} != 0))
   {
      # Set the "source" error-status and error-index
      $this->{'_error_status'} = $response->{'_error_status'};
      $this->{'_error_index'}  = $response->{'_error_index'};
      return $this->_snmp_decode_error(
         "Received SNMP %s error-status at error-index %s",
         _snmp_error_status_itoa($response->{'_error_status'}),
         $response->{'_error_index'}
      );
   }

   TRUE;
}

sub _snmp_decode_error
{
   my $this = shift;

   # Clear var_bind_list
   $this->_object_clear_var_bind_list;

   $this->_object_error(@_);
}

###
## Simple Network Management Protocol (SNMP) utilties
###

sub _snmp_error_status_itoa
{
   my $error = shift;

   my @error_status = qw(
      noError
      tooBig
      noSuchName
      badValue
      readOnly
      genError
      noAccess
      wrongType    
      wrongLength
      wrongEncoding
      wrongValue
      noCreation
      inconsistentValue
      resourceUnavailable
      commitFailed
      undoFailed
      authorizationError
      notWritable
      inconsistentName
   ); 

   if (!defined($error)) { return '??'; }
   if (($error > $#error_status) || ($error < 0)) { 
      return sprintf("??(%d)", $error); 
   }

   sprintf("%s(%d)", $error_status[$error], $error);
}


###
## Abstract Syntax Notation One (ASN.1) encode methods
###

my $ASN1_ENCODE_METHODS = {
   INTEGER,            '_asn1_encode_integer',
   OCTET_STRING,       '_asn1_encode_octet_string',
   NULL,               '_asn1_encode_null',
   OBJECT_IDENTIFIER,  '_asn1_encode_object_identifier',
   SEQUENCE,           '_asn1_encode_sequence',
   IPADDRESS,          '_asn1_encode_ipaddress',
   COUNTER,            '_asn1_encode_counter',
   GAUGE,              '_asn1_encode_gauge',
   TIMETICKS,          '_asn1_encode_timeticks',
   OPAQUE,             '_asn1_encode_opaque',
   COUNTER64,          '_asn1_encode_counter64',
   NOSUCHOBJECT,       '_asn1_encode_nosuchobject',
   NOSUCHINSTANCE,     '_asn1_encode_nosuchinstance',
   ENDOFMIBVIEW,       '_asn1_encode_endofmibview',
   GET_REQUEST,        '_asn1_encode_get_request',
   GET_NEXT_REQUEST,   '_asn1_encode_get_next_request',
   GET_RESPONSE,       '_asn1_encode_get_response',
   SET_REQUEST,        '_asn1_encode_set_request',
   TRAP,               '_asn1_encode_trap',
   GET_BULK_REQUEST,   '_asn1_encode_get_bulk_request',
   INFORM_REQUEST,     '_asn1_encode_inform_request',
   SNMPV2_TRAP,        '_asn1_encode_v2_trap'
};

sub _asn1_encode
{
   my ($this, $type, $value) = @_;

   if (!defined($type)) {
      return $this->_asn1_encode_error('ASN.1 type not defined');
   }

   if (exists($ASN1_ENCODE_METHODS->{$type})) {
      my $method = $ASN1_ENCODE_METHODS->{$type};
      $this->$method($value);
   } else {
      $this->_asn1_encode_error("Unknown ASN.1 type [%s]", $type);
   }
}

sub _asn1_encode_type_length
{
   my ($this, $type, $value) = @_;
   my $length = 0;

   if (defined($this->{'_error'})) { return $this->_asn1_encode_error; }

   if (!defined($type)) {
      return $this->_asn1_Error('ASN.1 type not defined');
   }

   if (!defined($value)) { $value = ''; }

   $length = length($value);

   if ($length < 0x80) {
      return $this->_object_put_buffer(
         join('', pack('C2', $type, $length), $value)
      );
   } elsif ($length <= 0xff) {
      return $this->_object_put_buffer(
         join('', pack('C3', $type, (0x80 | 1), $length), $value)
      );
   } elsif ($length <= 0xffff) {
      return $this->_object_put_buffer(
         join('', pack('CCn', $type, (0x80 | 2), $length), $value)
      );
   } 
      
   $this->_asn1_encode_error('Unable to encode ASN.1 length');
}

sub _asn1_encode_integer
{
   my ($this, $integer) = @_;

   if (!defined($integer)) {
      return $this->_asn1_encode_error('INTEGER value not defined');
   }

   if ($integer !~ /^-?\d+$/) {
      return $this->_asn1_encode_error('Expected numeric INTEGER value');
   }

   $this->_asn1_encode_integer32(INTEGER, $integer);
}

sub _asn1_encode_unsigned32
{
   my ($this, $type, $u_int32) = @_;

   if (!defined($type)) { $type = INTEGER; }

   if (!defined($u_int32)) { 
      return $this->_asn1_encode_error(
         "%s value not defined", _asn1_itoa($type) 
      );
   }

   if ($u_int32 !~ /^\d+$/) {
      return $this->_asn1_encode_error(
         "Expected positive numeric %s value", _asn1_itoa($type)
      );
   }

   $this->_asn1_encode_integer32($type, $u_int32);
}

sub _asn1_encode_integer32
{
   my ($this, $type, $integer) = @_;
   my ($size, $value, $negative, $prefix) = (4, '', FALSE, FALSE);

   if (!defined($type)) { $type = INTEGER; }

   if (!defined($integer)) {
      return $this->_asn1_encode_error(
         "%s value not defined", _asn1_itoa($type)
      );
   }

   # Determine if the value is positive or negative
   if ($integer =~ /^-/) { $negative = TRUE; } 

   # Check to see if the most significant bit is set, if it is we
   # need to prefix the encoding with a zero byte.

   if (((($integer & 0xff000000) >> 24) & 0x80) && (!$negative)) { 
      $size++; 
      $prefix = TRUE;
   }

   # Remove occurances of nine consecutive ones (if negative) or zeros 
   # from the most significant end of the two's complement integer.

   while ((((!($integer & 0xff800000))) || 
           ((($integer & 0xff800000) == 0xff800000) && ($negative))) && 
           ($size > 1)) 
   {
      $size--;
      $integer <<= 8;
   }

   # Add a zero byte so the integer is decoded as a positive value
   if ($prefix) {
      $value .= pack('x');
      $size--;
   }

   # Build the integer
   while ($size-- > 0) {
      $value .= pack('C', (($integer & 0xff000000) >> 24));
      $integer <<= 8;
   }

   # Encode ASN.1 header
   $this->_asn1_encode_type_length($type, $value);
}

sub _asn1_encode_octet_string
{
   if (!defined($_[1])) {
      return $_[0]->_asn1_encode_error('OCTET STRING value not defined');
   }

   $_[0]->_asn1_encode_type_length(OCTET_STRING, $_[1]);
}

sub _asn1_encode_null 
{ 
   $_[0]->_asn1_encode_type_length(NULL, ''); 
}

sub _asn1_encode_object_identifier
{
   my ($this, $oid) = @_;
   my ($value, $subid, $mask, $bits, $tmask, $tbits) = ('', 0, 0, 0, 0, 0);

   if (!defined($oid)) {
      return $this->_asn1_Error('OBJECT IDENTIFIER value not defined');
   }

   # Input is expected in dotted notation, so break it up into subids
   my @subids = split(/\./, $oid);

   # If there was a leading dot on _any_ OBJECT IDENTIFIER passed to 
   # an encode method, return a leading dot on _all_ of the OBJECT
   # IDENTIFIERs in the decode methods.

   if ($subids[0] eq '') { 
      DEBUG_INFO("leading dot present");
      $this->{'_leading_dot'} = TRUE;
      shift(@subids);
   }

   # The first two subidentifiers are encoded into the first identifier
   # using the the equation: subid = ((first * 40) + second).  We just
   # return an error if there are not at least two subidentifiers.

   if (scalar(@subids) < 2) { 
      return $this->_asn1_encode_error('Invalid OBJECT IDENTIFIER length'); 
   }

   $value = 40 * shift(@subids);
   $value = pack('C', ($value + shift(@subids)));

   # Encode each value as seven bits with the most significant bit
   # indicating the end of a subidentifier.

   foreach $subid (@subids) {
      if (($subid < 0x7f) && ($subid >= 0)) {
         $value .= pack('C', $subid);
      } else {
         $mask = 0x7f;
         $bits = 0;
         # Determine the number of bits need to encode the subidentifier
         for ($tmask = 0x7f, $tbits = 0; 
              $tmask != 0x00; 
              $tmask <<= 7, $tbits += 7)
         {
            if ($subid & $tmask) {
               $mask = $tmask;
               $bits = $tbits;
            }
         }
         # Now encode it, using the number of bits from above
         for ( ; $mask != 0x7f; $mask >>= 7, $bits -= 7) {
            # Handle a mask that was truncated above because
            # the subidentifier was four bytes long.
            if ((($mask & 0xffffffff) == 0xffe00000) ||
                 ($mask == 0x1e00000)) 
            {
               $mask = 0xfe00000;
            }
            $value .= pack('C', ((($subid & $mask) >> $bits) | 0x80));
         }
         $value .= pack('C', ($subid & $mask));
      }
   }

   # Encode the ASN.1 header
   $this->_asn1_encode_type_length(OBJECT_IDENTIFIER, $value);
}

sub _asn1_encode_sequence 
{ 
   $_[0]->_asn1_encode_type_length(SEQUENCE, $_[1]); 
}

sub _asn1_encode_ipaddress
{
   my ($this, $address) = @_;

   if (!defined($address)) {
      return $this->_asn1_encode_error('IpAddress not defined');
   }

   if ($address  !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
      return $this->_asn1_encode_error('Expected IpAddress in dotted notation');
   } 

   $this->_asn1_encode_type_length(
      IPADDRESS, pack('C4', split(/\./, $address))
   );
}

sub _asn1_encode_counter 
{ 
   $_[0]->_asn1_encode_unsigned32(COUNTER, $_[1]); 
}

sub _asn1_encode_gauge   
{ 
   $_[0]->_asn1_encode_unsigned32(GAUGE, $_[1]);   
}

sub _asn1_encode_timeticks 
{ 
   $_[0]->_asn1_encode_unsigned32(TIMETICKS, $_[1]); 
}

sub _asn1_encode_opaque
{
   if (!defined($_[1])) {
      return $_[0]->_asn1_encode_error('Opaque value not defined');
   }

   $_[0]->_asn1_encode_type_length(OPAQUE, $_[1]);
}

sub _asn1_encode_counter64
{
   my ($this, $u_int64) = @_;
   my ($quotient, $remainder) = (0, 0);
   my @bytes = ();

   # Validate the SNMP version
   if ($this->version != SNMP_VERSION_2C) {
      return $this->_asn1_encode_error(
         'Counter64 only supported with SNMPv2c'
      );
   }

   # Validate the passed value
   if (!defined($u_int64)) {
      return $this->_asn1_encode_error('Counter64 value not defined');
   }

   if ($u_int64 !~ /^\+?\d+$/) {
      return $this->_asn1_encode_error(
         'Expected positive numeric Counter64 value'
      );
   }

   # Only load the Math::BigInt module when needed
   use Math::BigInt;

   $u_int64 = Math::BigInt->new($u_int64);

   if ($u_int64 eq 'NaN') {
      return $this->_asn1_encode_error('Invalid Counter64 value');
   }

   # Make sure the value is no more than 8 bytes long
   if ($u_int64->bcmp('18446744073709551615') > 0) {
      return $this->_asn1_encode_error('Counter64 value too high');
   }

   if ($u_int64 == 0) { unshift(@bytes, 0x00); }

   while ($u_int64 > 0) {
      ($quotient, $remainder) = $u_int64->bdiv(256);
      $u_int64 = Math::BigInt->new($quotient);
      unshift(@bytes, $remainder);
   }

   # Make sure that the value is encoded as a positive value
   if ($bytes[0] & 0x80) { unshift(@bytes, 0x00); }

   $this->_asn1_encode_type_length(COUNTER64, pack('C*', @bytes));
}

sub _asn1_encode_nosuchobject
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'noSuchObject only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(NOSUCHOBJECT, '');
}

sub _asn1_encode_nosuchinstance
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'noSuchInstance only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(NOSUCHINSTANCE, '');
}

sub _asn1_encode_endofmibview
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'endOfMibView only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(ENDOFMIBVIEW, '');
}

sub _asn1_encode_get_request
{
   $_[0]->_asn1_encode_type_length(GET_REQUEST, $_[1]);
}

sub _asn1_encode_get_next_request
{
   $_[0]->_asn1_encode_type_length(GET_NEXT_REQUEST, $_[1]);
}

sub _asn1_encode_get_response
{
   $_[0]->_asn1_encode_type_length(GET_RESPONSE, $_[1]);
}

sub _asn1_encode_set_request
{
   $_[0]->_asn1_encode_type_length(SET_REQUEST, $_[1]);
}

sub _asn1_encode_trap
{
   $_[0]->_asn1_encode_type_length(TRAP, $_[1]);
}

sub _asn1_encode_get_bulk_request
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'GetBulkRequest-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(GET_BULK_REQUEST, $_[1]);
}

sub _asn1_encode_inform_request
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'InformRequest-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(INFORM_REQUEST, $_[1]);
}

sub _asn1_encode_v2_trap
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_encode_error(
         'SNMPv2-Trap-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_encode_type_length(SNMPV2_TRAP, $_[1]);
}

sub _asn1_encode_error
{
   my $this = shift;

   # Clear the buffer
   $this->_object_clear_buffer;

   $this->_object_error(@_);
}


###
## Abstract Syntax Notation One (ASN.1) decode methods
###

my $ASN1_DECODE_METHODS = {
   INTEGER,            '_asn1_decode_integer32',
   OCTET_STRING,       '_asn1_decode_octet_string',
   NULL,               '_asn1_decode_null',
   OBJECT_IDENTIFIER,  '_asn1_decode_object_identifier',
   SEQUENCE,           '_asn1_decode_sequence',
   IPADDRESS,          '_asn1_decode_ipaddress',
   COUNTER,            '_asn1_decode_counter',
   GAUGE,              '_asn1_decode_gauge',
   TIMETICKS,          '_asn1_decode_timeticks',
   OPAQUE,             '_asn1_decode_opaque',
   COUNTER64,          '_asn1_decode_counter64',
   NOSUCHOBJECT,       '_asn1_decode_nosuchobject',
   NOSUCHINSTANCE,     '_asn1_decode_nosuchinstance',
   ENDOFMIBVIEW,       '_asn1_decode_endofmibview',
   GET_REQUEST,        '_asn1_decode_get_request',
   GET_NEXT_REQUEST,   '_asn1_decode_get_next_request',
   GET_RESPONSE,       '_asn1_decode_get_response',
   SET_REQUEST,        '_asn1_decode_set_request',
   TRAP,               '_asn1_decode_trap',
   GET_BULK_REQUEST,   '_asn1_decode_get_bulk_request',
   INFORM_REQUEST,     '_asn1_decode_inform_request',
   SNMPV2_TRAP,        '_asn1_decode_v2_trap'
};

sub _asn1_decode
{
   my ($this, $expected) = @_;

   if (defined($this->{'_error'})) { return $this->_asn1_decode_error; }

   my $type = $this->_object_get_buffer(1);

   if (defined($type)) {
      $type = unpack('C', $type);
      if (exists($ASN1_DECODE_METHODS->{$type})) {
         my $method = $ASN1_DECODE_METHODS->{$type};
         if (defined($expected)) {
            if ($type != $expected) {
               return $this->_asn1_decode_error(
                  "Expected %s, but found %s", 
                  _asn1_itoa($expected), _asn1_itoa($type)
               );
            }
         }
         return $this->$method($type);
      } else {
         return $this->_asn1_decode_error("Unknown ASN.1 type [0x%02x]", $type);
      }
   }
 
   $this->_asn1_decode_error;
}

sub _asn1_decode_length
{
   my $this = shift;
   my ($length, $byte_cnt) = (0, 0);
  
   if (defined($this->{'_error'})) { return $this->_asn1_decode_error; }

   if (!defined($length = $this->_object_get_buffer(1))) {
      return $this->_asn1_decode_error;
   } 
   $length = unpack('C', $length);
 
   if ($length & 0x80) {
      $byte_cnt = ($length & 0x7f);
      if ($byte_cnt == 0) {
         return $this->_asn1_decode_error(
            'Indefinite ASN.1 lengths not supported'
         );  
      } elsif ($byte_cnt <= 4) {
         if (!defined($length = $this->_object_get_buffer($byte_cnt))) {
            return $this->_asn1_decode_error;
         }
         $length = unpack('N', ("\0" x (4 - $byte_cnt) . $length)); 
      } else {   
         return $this->_asn1_decode_error(
            "ASN.1 length too long (%d bytes)", $byte_cnt 
         );
      }
   }
 
   $length;
}

sub _asn1_decode_integer32
{
   my ($this, $type) = @_;
   my ($length, $integer, $negative, $byte) = (undef, 0, FALSE, undef);

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   # Return an error if the object length is zero
   if ($length < 1) {
      return $this->_asn1_decode_error(
         "%s length equal to zero", _asn1_itoa($type)
      );
   } 
      
   if (!defined($byte = $this->_object_get_buffer(1))) { 
      return $this->_asn1_decode_error; 
   }
   $length--;

   # If the first bit is set, the integer is negative
   if (($byte = unpack('C', $byte)) & 0x80) {
      $integer = -1;
      $negative = TRUE; 
   }

   if (($length > 4) || (($length > 3) && ($byte != 0x00))) {
      return $this->_asn1_decode_error(
         "%s length too long (%d bytes)", _asn1_itoa($type), ($length + 1)
      );
   }

   $integer = (($integer << 8) | $byte);

   while ($length--) {
      if (!defined($byte = $this->_object_get_buffer(1))) {
         return $this->_asn1_decode_error;
      }
      $integer = (($integer << 8) | unpack('C', $byte));
   }
 
   if ($negative) { 
      sprintf("%d", $integer); 
   } else {
      $integer = abs($integer);
      sprintf("%u", $integer);
   }
}

sub _asn1_decode_octet_string
{
   my ($this, $type) = @_;
   my ($length, $string) = (undef, undef);

   if (!defined($type)) { $type = OCTET_STRING; }

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if (defined($string = $this->_object_get_buffer($length))) {
      if (($string =~ /[\x01-\x08\x0b\x0e-\x1f\x7f-\xff]/g) && 
          ($this->translate)) 
      {
         DEBUG_INFO(
            "translating %s to printable hex string", _asn1_itoa($type)
         );
         return sprintf("0x%s", unpack('H*', $string));
      } else {
         return $string;
      }
   } else {
      return $this->_asn1_decode_error;
   }
}

sub _asn1_decode_null
{
   my $this = shift;
   my ($length) = (undef);

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length != 0) {
      return $this->_asn1_decode_error('NULL length not equal to zero');
   }

   if ($this->translate) {
      DEBUG_INFO("translating NULL to 'NULL' string");
      'NULL';
   } else {
      '';
   }
}

sub _asn1_decode_object_identifier
{
   my $this = shift;
   my ($length, $subid_cnt, $subid, $byte) = (undef, 1, 0, undef);
   my (@oid);

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length < 1) { 
      return $this->_asn1_decode_error(
         'OBJECT IDENTIFIER length equal to zero'
      );
   }

   while ($length > 0) {
      $subid = 0;
      do {
         if (!defined($byte = $this->_object_get_buffer(1))) {
            return $this->_asn1_decode_error;
         }   
         $byte = unpack('C', $byte);
         if ($subid >= 0xffffffff) {
            return $this->_asn1_decode_error(
               'OBJECT IDENTIFIER subidentifier too large'
            );
         }
         $subid = (($subid << 7) + ($byte & 0x7f)); 
         $length--;
      } while ($byte & 0x80);
      $oid[$subid_cnt++] = $subid;
   }

   # The first two subidentifiers are encoded into the first identifier
   # using the the equation: subid = ((first * 40) + second).

   $subid  = $oid[1];
   $oid[1] = int($subid % 40);
   $oid[0] = int(($subid - $oid[1]) / 40);

   # Return the OID in dotted notation (optionally with a leading dot
   # if one was passed to the encode routine).

   if ($this->{'_leading_dot'}) {
      DEBUG_INFO("adding leading dot");
      '.' . join('.', @oid);
   } else {
      join('.', @oid);
   }
}

sub _asn1_decode_sequence
{
   # Return the length, instead of the value
   $_[0]->_asn1_decode_length;
}

sub _asn1_decode_ipaddress
{
   my $this = shift;
   my ($length, $address) = (undef, undef);

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length != 4) {
      return $this->_asn1_decode_error(
         "Invalid IpAddress length (% byte%s)", 
         $length, ($length == 1 ? '' : 's')
      );
   }

   if (defined($address = $this->_object_get_buffer(4))) {
      join('.', unpack('C4', $address));
   } else {
      $this->_asn1_decode_error;
   }
}

sub _asn1_decode_counter 
{ 
   $_[0]->_asn1_decode_integer32(COUNTER); 
}

sub _asn1_decode_gauge   
{ 
   $_[0]->_asn1_decode_integer32(GAUGE); 
}

sub _asn1_decode_timeticks
{
   my $this = shift;
   my $ticks = undef;

   if (defined($ticks = $this->_asn1_decode_integer32(TIMETICKS))) {
      if ($this->translate) {
         DEBUG_INFO("translating %u TimeTicks to time\n", $ticks);
         return _asn1_ticks_to_time($ticks);
      } else {
         return $ticks;
      }
   } else {
      return $this->_asn1_decode_error;
   }
}

sub _asn1_decode_opaque 
{ 
   $_[0]->_asn1_decode_octet_string(OPAQUE); 
}

sub _asn1_decode_counter64
{
   my $this = shift;
   my ($length, $byte, $negative) = (undef, undef, FALSE);

   # Verify the SNMP version
   if ($this->version != SNMP_VERSION_2C) {
      return $this->_asn1_decode_error(
         'Counter64 only supported with SNMPv2c'
      );
   }

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length < 1) {
      return $this->_asn1_decode_error('Counter64 length equal to zero');
   }

   if (!defined($byte = $this->_object_get_buffer(1))) {
      return $this->_asn1_decode_error;
   }
   $length--;
   $byte = unpack('C', $byte);

   if (($length > 8) || (($length > 7) && ($byte != 0x00))) {
      return $this->_asn1_decode_error(
         "Counter64 length too long (%d bytes)", ($length + 1)
      );
   }

   # Only load the Math::BigInt module when needed
   use Math::BigInt;

   if ($byte & 0x80) { 
      $negative = TRUE;
      $byte = $byte ^ 0xff; 
   }

   my $u_int64 = Math::BigInt->new($byte);

   while ($length-- > 0) {
      if (!defined($byte = $this->_object_get_buffer(1))) {
         return $this->_asn1_decode_error;
      }
      $byte = unpack('C', $byte);
      if ($negative) { $byte = $byte ^ 0xff; }
      $u_int64 = $u_int64->bmul(256);
      $u_int64 = Math::BigInt->new($u_int64);
      $u_int64 = $u_int64->badd($byte);
      $u_int64 = Math::BigInt->new($u_int64);
   };

   # If the value is negative the other end incorrectly encoded  
   # the Counter64 since it should always be a positive value. 

   if ($negative) {
      $byte = Math::BigInt->new('-1');
      $u_int64 = $byte->bsub($u_int64);
   }

   # Remove the plus sign (or should we leave it to imply Math::BigInt?)
   $u_int64 =~ s/^\+//;

   $u_int64;
}

sub _asn1_decode_nosuchobject
{
   my $this = shift;
   my ($length) = (undef);

   if ($this->version != SNMP_VERSION_2C) {
      return $this->_asn1_decode_error(
         'noSuchObject only supported with SNMPv2c'
      );
   }
 
   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length != 0) {
      return $this->_asn1_decode_error('noSuchObject length not equal to zero');
   }

   if ($this->translate) {
      DEBUG_INFO("translating noSuchObject to 'noSuchObject' string");
      'noSuchObject';
   } else {
      $this->{'_error_status'} = NOSUCHOBJECT;
      '';
   }
}

sub _asn1_decode_nosuchinstance
{
   my $this = shift;
   my ($length) = (undef);

   if ($this->version != SNMP_VERSION_2C) {
      return $this->_asn1_decode_error(
         'noSuchInstance only supported with SNMPv2c'
      );
   }

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length != 0) {
      return $this->_asn1_decode_error(
         'noSuchInstance length not equal to zero'
      );
   }

   if ($this->translate) {
      DEBUG_INFO("translating noSuchInstance to 'noSuchInstance' string");
      'noSuchInstance';
   } else {
      $this->{'_error_status'} = NOSUCHINSTANCE;
      '';
   }
}

sub _asn1_decode_endofmibview
{
   my $this = shift;
   my ($length) = (undef);

   if ($this->version != SNMP_VERSION_2C) {
      return $this->_asn1_decode_error(
         'endOfMibView only supported with SNMPv2c'
      );
   }

   if (!defined($length = $this->_asn1_decode_length)) {
      return $this->_asn1_decode_error;
   }

   if ($length != 0) {
      return $this->_asn1_decode_error('endOfMibView length not equal to zero');
   }

   if ($this->translate) {
      DEBUG_INFO("translating endOfMibView to 'endOfMibView' string");
      'endOfMibView';
   } else {
      $this->{'_error_status'} = ENDOFMIBVIEW;
      '';
   }
}

sub _asn1_decode_pdu
{
   # Generic methods used to decode the PDU type.  The ASN.1 type is
   # returned by the method as passed by the generic decode routine.

   if ((defined($_[0]->_asn1_decode_length)) && (defined($_[1]))) {
      $_[1];
   } else {
      $_[0]->_asn1_decode_error('ASN.1 PDU type not defined');
   } 
}

sub _asn1_decode_get_request      
{ 
   $_[0]->_asn1_decode_pdu(GET_REQUEST); 
}

sub _asn1_decode_get_next_request 
{ 
   $_[0]->_asn1_decode_pdu(GET_NEXT_REQUEST); 
}

sub _asn1_decode_get_response      
{ 
   $_[0]->_asn1_decode_pdu(GET_RESPONSE); 
}

sub _asn1_decode_set_request      
{ 
   $_[0]->_asn1_decode_pdu(SET_REQUEST); 
}

sub _asn1_decode_trap             
{ 
   $_[0]->_asn1_decode_pdu(TRAP); 
}

sub _asn1_decode_get_bulk_request 
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_decode_error(
         'GetBulkRequest-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_decode_pdu(GET_BULK_REQUEST); 
}

sub _asn1_decode_inform_request
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_decode_error(
         'InformRequest-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_decode_pdu(INFORM_REQUEST);
}

sub _asn1_decode_v2_trap
{
   if ($_[0]->version != SNMP_VERSION_2C) {
      return $_[0]->_asn1_decode_error(
         'SNMPv2-Trap-PDU only supported with SNMPv2c'
      );
   }

   $_[0]->_asn1_decode_pdu(SNMPV2_TRAP);
}

sub _asn1_decode_error
{
   my $this = shift;

   $this->_object_error(@_);
}


###
## Abstract Syntax Notation One (ASN.1) utility functions 
###

sub _asn1_itoa 
{
   my $type = shift;

   my $types = {
      INTEGER,            'INTEGER', 
      OCTET_STRING,       'OCTET STRING', 
      NULL,               'NULL', 
      OBJECT_IDENTIFIER,  'OBJECT IDENTIFER', 
      SEQUENCE,           'SEQUENCE', 
      IPADDRESS,          'IpAddress', 
      COUNTER,            'Counter', 
      GAUGE,              'Gauge', 
      TIMETICKS,          'TimeTicks', 
      OPAQUE,             'Opaque', 
      COUNTER64,          'Counter64',
      NOSUCHOBJECT,       'noSuchObject',
      NOSUCHINSTANCE,     'noSuchInstance',
      ENDOFMIBVIEW,       'endOfMibView',
      GET_REQUEST,        'GetRequest-PDU', 
      GET_NEXT_REQUEST,   'GetNextRequest-PDU', 
      GET_RESPONSE,       'GetResponse-PDU', 
      SET_REQUEST,        'SetRequest-PDU', 
      TRAP,               'Trap-PDU',
      GET_BULK_REQUEST,   'GetBulkRequest-PDU',
      INFORM_REQUEST,     'InformRequest-PDU',
      SNMPV2_TRAP,        'SNMPv2-Trap-PDU' 
   };

   if (!defined($type)) { return '??'; }

   if (exists($types->{$type})) {
      $types->{$type};
   } else {
      sprintf("?? [0x%02x]", $type);
   }
}

sub _asn1_oid_context_match
{
   my ($oid_p, $oid_c) = @_;
   my ($parent, $child) = (undef, undef);

   # Compares the parent OID (oid_p) to the child OID (oid_c)
   # and returns true if the child is equal to or is a subtree 
   # of the parent OID.
    
   if (!defined($oid_p)) { return FALSE; }
   if (!defined($oid_c)) { return FALSE; }

   # Remove leading dots
   $oid_p =~ s/^\.//;
   $oid_c =~ s/^\.//;

   my @subid_p = split(/\./, $oid_p);
   my @subid_c = split(/\./, $oid_c);

   while (@subid_p) {
      if (!defined($parent = shift(@subid_p))) { return TRUE; }
      if (!defined($child  = shift(@subid_c))) { return FALSE; }
      if ($parent != $child) { return FALSE; }
   }

   TRUE;
}

sub _asn1_oid_lex_sort 
{ 
   sort _asn1_lexicographical @_; 
}

sub _asn1_lexicographical
{
   my ($aa, $bb);

   my @a = split(/\./, $a);
   if ($a[0] eq '') { shift(@a); }

   my @b = split(/\./, $b);  
   if ($b[0] eq '') { shift(@b); } 

   while (@a) {
      if (!defined($aa = shift(@a))) { return -1; }
      if (!defined($bb = shift(@b))) { return 1;  }
      if ($aa < $bb) { return -1; } 
      if ($aa > $bb) { return 1;  }
   }

   0;
}

sub _asn1_ticks_to_time 
{
   my $ticks = shift;

   if (!defined($ticks)) { $ticks = 0; }

   my $days = int($ticks / (24 * 60 * 60 * 100));
   $ticks %= (24 * 60 * 60 * 100);

   my $hours = int($ticks / (60 * 60 * 100));
   $ticks %= (60 * 60 * 100);

   my $minutes = int($ticks / (60 * 100));
   $ticks %= (60 * 100);

   my $seconds = ($ticks / 100);

   if ($days != 0){
      sprintf("%d day%s, %02d:%02d:%05.02f", $days,
         ($days == 1 ? '' : 's'), $hours, $minutes, $seconds);
   } elsif ($hours != 0) {
      sprintf("%d hour%s, %02d:%05.02f", $hours,
         ($hours == 1 ? '' : 's'), $minutes, $seconds);
   } elsif ($minutes != 0) {
      sprintf("%d minute%s, %05.02f", $minutes, 
         ($minutes == 1 ? '' : 's'), $seconds);
   } else {
      sprintf("%04.02f second%s", $seconds, ($seconds == 1 ? '' : 's'));
   }

}


###
## User Datagram Protocol (UDP) methods
###

sub _udp_send_message
{
   my $this = shift;
   my ($retries, $rout, $rin) = (0, '', '');

   # Make sure the socket is still open
   if (!defined($this->{'_socket'})) {
      return $this->_udp_error('Session is closed');
   }

   # Get the number of retries
   $retries = $this->{'_retries'};

   # Setup a vector to indicate received data on the socket
   vec($rin, fileno($this->{'_socket'}), 1) = 1;

   do {
      if (!defined($this->_udp_send_buffer)) { return $this->_udp_error; }
      if (select($rout=$rin, undef, undef, $this->{'_timeout'})) {
         return $this->_udp_recv_buffer;
      } else {
         DEBUG_INFO("request timed out, retries = %d", $retries);
         $retries--;
      }
   } while ($retries >= 0);

   # Exceeded the number of retries
   $this->_udp_error(
      "No response from agent on remote host '%s'", $this->hostname 
   );
}

sub _udp_send_buffer
{
   my $this = shift;
   my ($length, $host_port, $host_addr) = (0, undef, undef);

   # Make sure the socket is still open
   if (!defined($this->{'_socket'})) {
      return $this->_udp_error('Session is closed');
   }

   ($host_port, $host_addr) = sockaddr_in($this->{'_sockaddr'});
   DEBUG_INFO("address %s, port %d", inet_ntoa($host_addr), $host_port); 
   $this->_debug_dump_buffer;

   # Transmit the contents of the buffer
   if (!defined($length = 
         send($this->{'_socket'}, $this->{'_buffer'}, 0, $this->{'_sockaddr'})
      ))
   {
      return $this->_udp_error("send(): %s", $!);
   }

   # Return the number of bytes transmitted
   $length;
}

sub _udp_recv_buffer
{
   my $this = shift;
   my $sockaddr = undef;

   # Make sure the socket is still open
   if (!defined($this->{'_socket'})) {
      return $this->_udp_error('Session is closed');
   }

   # Clear the contents of the buffer
   $this->_object_clear_buffer;

   # Fill the buffer
   if (!defined($sockaddr = 
         recv($this->{'_socket'}, $this->{'_buffer'}, $this->{'_mtu'}, 0)
      ))
   {
      return $this->_udp_error("recv(): %s", $!);
   }

   my ($host_port, $host_addr) = sockaddr_in($sockaddr);
   DEBUG_INFO("address %s, port %d", inet_ntoa($host_addr), $host_port);
   $this->_debug_dump_buffer;

   # Return the address structure
   $sockaddr;
}

sub _udp_error
{
   my $this = shift;

   $this->_object_error(@_);
}


###
## Object specific methods
###

sub _object_copy
{
   my $this = shift;

   my $copy = bless {}, ref($this);

   foreach (keys(%{$this})) { $copy->{$_} = $this->{$_}; }

   $copy;
}

sub _object_queue_message
{
   my $this = shift;

   if ((!$this->{'_nonblocking'}) || (!defined($this->{'_fsm'}))) {
      return $this->_object_encode_error(
         'Unable to queue message for a blocking session'
      );
   }

   if (!defined($this->{'_socket'})) { 
      return $this->_object_encode_error('Session is closed');
   }

   if (!defined($this->{'_fsm'}->queue_message($this))) {
      $this->_object_encode_error('Failed to queue message');
   }

   TRUE;
}

sub _object_add_callback
{
   my ($this, $callback) = @_;

   # Callbacks can be passed in two different ways.  If the callback
   # has options, the callback must be passed as an ARRAY reference
   # with the first element being a CODE reference and the remaining
   # elements the arguments.  If the callback has not options it
   # is just passed as a CODE reference.

   if ((ref($callback) eq 'ARRAY') && (ref($callback->[0]) eq 'CODE')) {
      $this->{'_callback'} = $callback;   
   } elsif (ref($callback) eq 'CODE') {
      $this->{'_callback'} = [$callback];
   } elsif (!defined($callback)) {
      $this->{'_callback'} = undef;  # Used to clear the callback
   } else {
      return $this->_object_error('Invalid callback format');
   }

   TRUE;
}

sub _object_invoke_callback
{
   my $this = shift;

   # Callbacks are invoked with a reference to the copy of the original 
   # object followed by the users parameters.

   if (defined($this->{'_callback'})) {
      my @argv = @{$this->{'_callback'}};
      my $callback = shift(@argv);
      if (ref($callback) ne 'CODE') { return($this->{'_callback'} = undef); } 
      unshift(@argv, $this);
      eval { &{$callback}(@argv) };
      if ($@ ne '') { DEBUG_INFO("eval error: %s", $@); }
      return $@;
   }
   DEBUG_INFO("callback not defined");

   $this->{'_callback'};
}

sub _object_get_table_cb
{
   my ($this, $argv) = @_;

   # Use get-next-requests until the response is not a subtree of the
   # base OBJECT IDENTIFIER.  Return the table only if there are no
   # errors other than a noSuchName(2) error since the table could
   # be at the end of the tree.  Also return the table when the value
   # of the OID equals endOfMibView(2) when using SNMPv2c.

   # Assign the "real" callback to the object
   $this->{'_callback'} = $argv->{'callback'};

   # Check to see if the var_bind_list is defined (was there an error?)

   if (defined($this->var_bind_list)) {

      my ($next_oid) = keys(%{$this->var_bind_list});
      if (_asn1_oid_context_match($argv->{'base_oid'}, $next_oid)) {

         if (!exists($argv->{'table'}->{$next_oid})) {
            $argv->{'table'}->{$next_oid} = $this->var_bind_list->{$next_oid};
         } elsif ((($this->var_bind_list->{$next_oid} eq 'endOfMibView') ||
                  ($this->error_status == ENDOFMIBVIEW)) &&        
                  ($this->version == SNMP_VERSION_2C))
         {
            if (!defined($argv->{'table'})) {
               $this->_object_decode_error('Requested table is empty');
            }
            $this->{'_var_bind_list'} = $argv->{'table'};
            return $this->_object_invoke_callback;
         } else {
            $argv->{'repeat_cnt'}++;
         }
         # Check to make sure that the remote host does not respond
         # incorrectly causing the get-next-requests to loop forever.
         if ($argv->{'repeat_cnt'} > 5) {
            $this->_object_decode_error(
               'Loop detected with table on remote host'
            );
            return $this->_object_invoke_callback;
         }
         # Queue up the get-next-request
         $this->get_next_request(
            -callback    => [\&_object_get_table_cb, $argv],
            -varbindlist => [$next_oid]
         );

      } else {
         # We have reached the end of the table
         if (!defined($argv->{'table'})) {
            $this->_object_decode_error('Requested table is empty');
         }
         $this->{'_var_bind_list'} = $argv->{'table'};
         return $this->_object_invoke_callback;
      }

   } else {

      # Check for noSuchName(2) error
      if ($this->error_status == 2) {
         if (!defined($argv->{'table'})) {
            $this->_object_decode_error('Requested table is empty');
         }
         $this->{'_var_bind_list'} = $argv->{'table'};
      }
      $this->_object_invoke_callback;

   }
}

sub _object_put_buffer
{
   my ($this, $prefix) = @_;

   # Do not do anything if there has already been an error
   if (defined($this->{'_error'})) { return $this->_object_encode_error; }

   # Make sure we do not exceed our MTU
   if (($this->_object_buffer_length + length($prefix)) > $this->{'_mtu'}) {
      return $this->_object_encode_error('PDU size exceeded MTU');
   }
 
   # Add the prefix to the current buffer
   if ((defined($prefix)) && ($prefix ne '')) {
      $this->{'_buffer'} = join('', $prefix, $this->{'_buffer'});
   } 

   # Return what was just added in case someone wants it
   $prefix;
}

sub _object_get_buffer
{
   my ($this, $offset) = @_;
   my $substr = '';

   # Do not do anything if there has already been an error
   if (defined($this->{'_error'})) { return $this->_object_decode_error; }
  
   # Either return the whole buffer or a sub-string from the 
   # beginning of the buffer and then set the buffer equal to
   # what is left in the buffer
 
   if (defined($offset)) {
      $offset = abs($offset);
      if ($offset > length($this->{'_buffer'})) {
         return $this->_object_decode_error('Unexpected end of buffer');
      } else {
         $substr = substr($this->{'_buffer'}, 0, $offset);
         $this->{'_buffer'} = substr($this->{'_buffer'}, $offset);
      }
   } else {
      $substr = $this->{'_buffer'};
      $this->{'_buffer'} = ''; 
   }

   $substr;
}

sub _object_clear_buffer 
{ 
   $_[0]->{'_buffer'} = ''; 
}

sub _object_clear_var_bind_list 
{ 
   $_[0]->{'_var_bind_list'} = undef; 
}

sub _object_clear_error
{
   $_[0]->{'_error_status'} = 0;
   $_[0]->{'_error_index'}  = 0;
   $_[0]->{'_error'} = undef;
}

sub _object_clear_snmp_message
{
   my @fields = qw(
      _version _community _type _request_id _error_status _error_index
      _enterprise _agent_addr _generic_trap _specific_trap _time_stamp
   );

   foreach (@fields) { if (exists($_[0]->{$_})) { $_[0]->{$_} = undef; } }
}

sub _object_clear_leading_dot 
{ 
   $_[0]->{'_leading_dot'} = FALSE; 
}

sub _object_buffer_length 
{ 
   length $_[0]->{'_buffer'}; 
}

sub _object_encode_error
{
   my $this = shift;

   # Clear the buffer
   $this->_object_clear_buffer;

   $this->_object_error(@_);
}

sub _object_decode_error
{
   my $this = shift;

   $this->_object_error(@_);
}

sub _object_error
{
   my ($this, $format, @message) = @_;

   if (!defined($this->{'_error'})) {
      $this->{'_error'} = sprintf $format, @message;
      if ($this->debug) {
         my $index = caller(3) ? 1 : 0; 
         printf("error: [%d] %s(): %s\n", 
            (caller($index))[2], (caller($index + 1))[3], $this->{'_error'} 
         );
      }
   }

   undef;
}


###
## Debug functions/methods
###

sub DEBUG_INFO
{
   if (!$Net::SNMP::DEBUG) { return $Net::SNMP::DEBUG; }

   my $format = sprintf("debug: [%d] %s(): ", (caller(0))[2], (caller(1))[3]);
   $format = join('', $format, shift(@_), "\n");
   printf $format, @_;

   $Net::SNMP::DEBUG;
}

sub _debug_dump_buffer
{
   my $this = shift;
   my ($length, $offset, $line, $hex) = (0, 0, '', '');

   if (!$this->debug) { return undef; }

   $length = length($this->{'_buffer'});

   DEBUG_INFO("%d byte%s", $length, ($length == 1 ? '' : 's'));
  
   while ($length > 0) {
      if ($length >= 16) { 
         $line = substr($this->{'_buffer'}, $offset, 16);
      } else {
         $line = substr($this->{'_buffer'}, $offset, $length);
      }
      $hex  = unpack('H*', $line);
      $hex .= ' ' x (32 - length($hex));
      $hex  = sprintf("%s %s %s %s  " x 4, unpack('a2' x 16, $hex));
      $line =~ s/[\x00-\x1f\x7f-\xff]/./g;
      printf("[%03d]  %s %s\n", $offset, uc($hex), $line);
      $offset += 16;
      $length -= 16;
   }
   print("\n");
   
   $this->{'_buffer'};
}

# ============================================================================
1; # [end Net::SNMP] 
